"""
Framework Detection Engine

Advanced pattern matching system for analyzing configuration files to determine
which extension should handle them. Supports XML, YAML, JSON with content analysis.
"""

import json
import logging
import re
import xml.etree.ElementTree as ET
from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from pathlib import Path
from typing import Dict, List, Optional, Set, Union, Any, Callable, Pattern
import yaml

logger = logging.getLogger(__name__)


@dataclass
class DetectionPattern:
    """Represents a pattern for detecting framework compatibility."""
    name: str
    pattern_type: str  # 'filename', 'content', 'xpath', 'yaml_path', 'json_path'
    pattern: str
    confidence: float = 1.0  # 0.0 to 1.0, higher means more specific
    required: bool = True
    description: Optional[str] = None
    
    def __post_init__(self):
        """Validate pattern after initialization."""
        if not 0.0 <= self.confidence <= 1.0:
            raise ValueError(f"Confidence must be between 0.0 and 1.0, got {self.confidence}")
        
        if self.pattern_type not in ['filename', 'content', 'xpath', 'yaml_path', 'json_path']:
            raise ValueError(f"Invalid pattern type: {self.pattern_type}")


@dataclass
class DetectionResult:
    """Result of framework detection analysis."""
    extension_name: str
    confidence: float
    matched_patterns: List[DetectionPattern] = field(default_factory=list)
    failed_patterns: List[DetectionPattern] = field(default_factory=list)
    metadata: Dict[str, Any] = field(default_factory=dict)
    errors: List[str] = field(default_factory=list)
    warnings: List[str] = field(default_factory=list)
    
    @property
    def success(self) -> bool:
        """Check if detection was successful."""
        return len(self.errors) == 0 and self.confidence > 0.0


class PatternMatcher(ABC):
    """Abstract base class for pattern matchers."""
    
    @abstractmethod
    def match(self, config_path: Path, pattern: DetectionPattern) -> Optional[Dict[str, Any]]:
        """
        Match a pattern against a configuration file.
        
        Args:
            config_path: Path to configuration file
            pattern: Pattern to match
            
        Returns:
            Match metadata if successful, None otherwise
        """
        pass


class FilenamePatternMatcher(PatternMatcher):
    """Matches patterns against filenames."""
    
    def match(self, config_path: Path, pattern: DetectionPattern) -> Optional[Dict[str, Any]]:
        """Match filename pattern."""
        try:
            import fnmatch
            if fnmatch.fnmatch(config_path.name, pattern.pattern):
                return {'matched_filename': config_path.name}
        except Exception as e:
            logger.debug(f"Filename pattern match failed for {config_path}: {e}")
        
        return None


class ContentPatternMatcher(PatternMatcher):
    """Matches regex patterns against file content."""
    
    def __init__(self):
        self._compiled_patterns: Dict[str, Pattern] = {}
    
    def match(self, config_path: Path, pattern: DetectionPattern) -> Optional[Dict[str, Any]]:
        """Match content pattern."""
        try:
            # Compile pattern if not cached
            if pattern.pattern not in self._compiled_patterns:
                self._compiled_patterns[pattern.pattern] = re.compile(
                    pattern.pattern, 
                    re.IGNORECASE | re.MULTILINE
                )
            
            compiled_pattern = self._compiled_patterns[pattern.pattern]
            
            # Read file content
            content = config_path.read_text(encoding='utf-8')
            
            # Find matches
            matches = compiled_pattern.findall(content)
            if matches:
                return {
                    'matches': matches,
                    'match_count': len(matches),
                    'content_preview': content[:200] + '...' if len(content) > 200 else content
                }
                
        except Exception as e:
            logger.debug(f"Content pattern match failed for {config_path}: {e}")
        
        return None


class XPathPatternMatcher(PatternMatcher):
    """Matches XPath patterns against XML files."""
    
    def match(self, config_path: Path, pattern: DetectionPattern) -> Optional[Dict[str, Any]]:
        """Match XPath pattern."""
        try:
            # Parse XML
            tree = ET.parse(config_path)
            root = tree.getroot()
            
            # Apply XPath
            elements = root.findall(pattern.pattern)
            if elements:
                results = []
                for element in elements:
                    element_data = {
                        'tag': element.tag,
                        'text': element.text,
                        'attributes': dict(element.attrib)
                    }
                    results.append(element_data)
                
                return {
                    'xpath_matches': results,
                    'match_count': len(results)
                }
                
        except ET.ParseError as e:
            logger.debug(f"XML parsing failed for {config_path}: {e}")
        except Exception as e:
            logger.debug(f"XPath pattern match failed for {config_path}: {e}")
        
        return None


class YamlPathPatternMatcher(PatternMatcher):
    """Matches JSON-path-like patterns against YAML files."""
    
    def match(self, config_path: Path, pattern: DetectionPattern) -> Optional[Dict[str, Any]]:
        """Match YAML path pattern."""
        try:
            # Load YAML content
            with open(config_path, 'r', encoding='utf-8') as f:
                data = yaml.safe_load(f)
            
            # Apply path pattern
            value = self._get_nested_value(data, pattern.pattern)
            if value is not None:
                return {
                    'yaml_path': pattern.pattern,
                    'matched_value': value,
                    'value_type': type(value).__name__
                }
                
        except yaml.YAMLError as e:
            logger.debug(f"YAML parsing failed for {config_path}: {e}")
        except Exception as e:
            logger.debug(f"YAML path pattern match failed for {config_path}: {e}")
        
        return None
    
    def _get_nested_value(self, data: Any, path: str) -> Any:
        """Get nested value from data using dot-separated path."""
        if not path:
            return data
        
        parts = path.split('.')
        current = data
        
        for part in parts:
            if isinstance(current, dict):
                if part in current:
                    current = current[part]
                else:
                    return None
            elif isinstance(current, list):
                try:
                    index = int(part)
                    if 0 <= index < len(current):
                        current = current[index]
                    else:
                        return None
                except ValueError:
                    return None
            else:
                return None
        
        return current


class JsonPathPatternMatcher(PatternMatcher):
    """Matches JSON-path patterns against JSON files."""
    
    def match(self, config_path: Path, pattern: DetectionPattern) -> Optional[Dict[str, Any]]:
        """Match JSON path pattern."""
        try:
            # Load JSON content
            with open(config_path, 'r', encoding='utf-8') as f:
                data = json.load(f)
            
            # Apply path pattern (similar to YAML path)
            value = self._get_nested_value(data, pattern.pattern)
            if value is not None:
                return {
                    'json_path': pattern.pattern,
                    'matched_value': value,
                    'value_type': type(value).__name__
                }
                
        except json.JSONDecodeError as e:
            logger.debug(f"JSON parsing failed for {config_path}: {e}")
        except Exception as e:
            logger.debug(f"JSON path pattern match failed for {config_path}: {e}")
        
        return None
    
    def _get_nested_value(self, data: Any, path: str) -> Any:
        """Get nested value from data using dot-separated path."""
        if not path:
            return data
        
        parts = path.split('.')
        current = data
        
        for part in parts:
            if isinstance(current, dict):
                if part in current:
                    current = current[part]
                else:
                    return None
            elif isinstance(current, list):
                try:
                    index = int(part)
                    if 0 <= index < len(current):
                        current = current[index]
                    else:
                        return None
                except ValueError:
                    return None
            else:
                return None
        
        return current


class FrameworkDetectionEngine:
    """
    Engine for detecting which extension should handle a configuration file.
    
    Uses multiple pattern matchers to analyze configuration files and determine
    the best extension match based on content, structure, and metadata.
    """
    
    def __init__(self):
        """Initialize the detection engine."""
        self._matchers: Dict[str, PatternMatcher] = {
            'filename': FilenamePatternMatcher(),
            'content': ContentPatternMatcher(),
            'xpath': XPathPatternMatcher(),
            'yaml_path': YamlPathPatternMatcher(),
            'json_path': JsonPathPatternMatcher(),
        }
        
        # Registry of extension patterns
        self._extension_patterns: Dict[str, List[DetectionPattern]] = {}
        
        logger.debug("Initialized FrameworkDetectionEngine")
    
    def register_extension_patterns(self, extension_name: str, patterns: List[DetectionPattern]) -> None:
        """
        Register detection patterns for an extension.
        
        Args:
            extension_name: Name of the extension
            patterns: List of detection patterns
        """
        if not patterns:
            logger.warning(f"No patterns provided for extension {extension_name}")
            return
        
        # Validate patterns
        for pattern in patterns:
            if pattern.pattern_type not in self._matchers:
                raise ValueError(f"Unknown pattern type: {pattern.pattern_type}")
        
        self._extension_patterns[extension_name] = patterns
        logger.debug(f"Registered {len(patterns)} patterns for extension {extension_name}")
    
    def detect_framework(self, config_path: Union[str, Path]) -> List[DetectionResult]:
        """
        Detect which extensions can handle the given configuration file.
        
        Args:
            config_path: Path to configuration file
            
        Returns:
            List of detection results sorted by confidence (highest first)
        """
        config_file = Path(config_path)
        
        if not config_file.exists():
            return [DetectionResult(
                extension_name='',
                confidence=0.0,
                errors=[f"Configuration file does not exist: {config_path}"]
            )]
        
        if not config_file.is_file():
            return [DetectionResult(
                extension_name='',
                confidence=0.0,
                errors=[f"Configuration path is not a file: {config_path}"]
            )]
        
        results = []
        
        # Test each registered extension
        for extension_name, patterns in self._extension_patterns.items():
            result = self._test_extension(config_file, extension_name, patterns)
            results.append(result)
        
        # Sort by confidence (highest first)
        results.sort(key=lambda r: r.confidence, reverse=True)
        
        logger.debug(f"Detected {len([r for r in results if r.success])} compatible extensions for {config_path}")
        return results
    
    def find_best_extension(self, config_path: Union[str, Path]) -> Optional[DetectionResult]:
        """
        Find the best extension to handle the configuration file.
        
        Args:
            config_path: Path to configuration file
            
        Returns:
            Best detection result or None if no suitable extension found
        """
        results = self.detect_framework(config_path)
        
        # Return the highest confidence successful result
        for result in results:
            if result.success:
                return result
        
        return None
    
    def validate_config_file(self, config_path: Union[str, Path]) -> Tuple[bool, List[str], List[str]]:
        """
        Validate a configuration file for general compatibility.
        
        Args:
            config_path: Path to configuration file
            
        Returns:
            Tuple of (is_valid, errors, warnings)
        """
        config_file = Path(config_path)
        errors = []
        warnings = []
        
        # Basic file validation
        if not config_file.exists():
            errors.append(f"Configuration file does not exist: {config_path}")
            return False, errors, warnings
        
        if not config_file.is_file():
            errors.append(f"Configuration path is not a file: {config_path}")
            return False, errors, warnings
        
        # Check file size
        try:
            file_size = config_file.stat().st_size
            if file_size == 0:
                errors.append("Configuration file is empty")
            elif file_size > 10 * 1024 * 1024:  # 10MB
                warnings.append(f"Configuration file is very large ({file_size} bytes)")
        except Exception as e:
            warnings.append(f"Could not check file size: {e}")
        
        # Try to parse as different formats
        format_tests = [
            ('XML', self._test_xml_format),
            ('YAML', self._test_yaml_format),
            ('JSON', self._test_json_format),
        ]
        
        parseable_formats = []
        for format_name, test_func in format_tests:
            try:
                if test_func(config_file):
                    parseable_formats.append(format_name)
            except Exception as e:
                logger.debug(f"Failed to test {format_name} format for {config_path}: {e}")
        
        if not parseable_formats:
            errors.append("Configuration file is not in a recognized format (XML, YAML, JSON)")
        elif len(parseable_formats) > 1:
            warnings.append(f"Configuration file appears to be valid in multiple formats: {parseable_formats}")
        
        return len(errors) == 0, errors, warnings
    
    def get_extension_info(self) -> Dict[str, Dict[str, Any]]:
        """
        Get information about registered extensions and their patterns.
        
        Returns:
            Dictionary with extension information
        """
        info = {}
        
        for extension_name, patterns in self._extension_patterns.items():
            pattern_info = []
            for pattern in patterns:
                pattern_info.append({
                    'name': pattern.name,
                    'type': pattern.pattern_type,
                    'pattern': pattern.pattern,
                    'confidence': pattern.confidence,
                    'required': pattern.required,
                    'description': pattern.description
                })
            
            info[extension_name] = {
                'pattern_count': len(patterns),
                'patterns': pattern_info,
                'required_patterns': sum(1 for p in patterns if p.required),
                'average_confidence': sum(p.confidence for p in patterns) / len(patterns)
            }
        
        return info
    
    def clear_extensions(self) -> None:
        """Clear all registered extensions."""
        self._extension_patterns.clear()
        logger.debug("Cleared all registered extensions")
    
    def _test_extension(self, config_file: Path, extension_name: str, 
                       patterns: List[DetectionPattern]) -> DetectionResult:
        """Test an extension against a configuration file."""
        result = DetectionResult(extension_name=extension_name, confidence=0.0)
        
        total_weight = 0.0
        matched_weight = 0.0
        required_patterns = [p for p in patterns if p.required]
        required_matched = 0
        
        for pattern in patterns:
            try:
                matcher = self._matchers[pattern.pattern_type]
                match_data = matcher.match(config_file, pattern)
                
                if match_data:
                    result.matched_patterns.append(pattern)
                    result.metadata[pattern.name] = match_data
                    matched_weight += pattern.confidence
                    
                    if pattern.required:
                        required_matched += 1
                else:
                    result.failed_patterns.append(pattern)
                
                total_weight += pattern.confidence
                
            except Exception as e:
                error_msg = f"Pattern {pattern.name} failed: {e}"
                result.errors.append(error_msg)
                result.failed_patterns.append(pattern)
                logger.debug(error_msg)
        
        # Calculate confidence
        if total_weight > 0:
            confidence = matched_weight / total_weight
            
            # Reduce confidence if required patterns are missing
            if required_patterns and required_matched < len(required_patterns):
                missing_ratio = 1.0 - (required_matched / len(required_patterns))
                confidence *= (1.0 - missing_ratio * 0.5)  # Reduce by up to 50%
            
            result.confidence = max(0.0, min(1.0, confidence))
        
        return result
    
    def _test_xml_format(self, config_file: Path) -> bool:
        """Test if file is valid XML."""
        try:
            ET.parse(config_file)
            return True
        except ET.ParseError:
            return False
    
    def _test_yaml_format(self, config_file: Path) -> bool:
        """Test if file is valid YAML."""
        try:
            with open(config_file, 'r', encoding='utf-8') as f:
                yaml.safe_load(f)
            return True
        except yaml.YAMLError:
            return False
    
    def _test_json_format(self, config_file: Path) -> bool:
        """Test if file is valid JSON."""
        try:
            with open(config_file, 'r', encoding='utf-8') as f:
                json.load(f)
            return True
        except json.JSONDecodeError:
            return False


# Global detection engine instance
detection_engine = FrameworkDetectionEngine()


def register_builtin_patterns():
    """Register built-in detection patterns for common frameworks."""
    
    # GCR-NCCL Original patterns
    gcr_patterns = [
        DetectionPattern(
            name='gcr_xml_root',
            pattern_type='xpath',
            pattern='.//Parameter[@name="solver_type"][@value="gcr"]',
            confidence=0.9,
            description='GCR solver type parameter in XML'
        ),
        DetectionPattern(
            name='gcr_nccl_content',
            pattern_type='content',
            pattern=r'(?i)(gcr|nccl|helmholtz)',
            confidence=0.7,
            description='GCR/NCCL/Helmholtz keywords in content'
        ),
        DetectionPattern(
            name='gcr_config_filename',
            pattern_type='filename',
            pattern='*gcr*.xml',
            confidence=0.6,
            description='GCR in filename'
        )
    ]
    
    # PETSc patterns
    petsc_patterns = [
        DetectionPattern(
            name='petsc_xml_parameter',
            pattern_type='xpath',
            pattern='.//Parameter[@name="framework"][@value="petsc"]',
            confidence=0.9,
            description='PETSc framework parameter'
        ),
        DetectionPattern(
            name='petsc_yaml_framework',
            pattern_type='yaml_path',
            pattern='framework',
            confidence=0.8,
            description='Framework field in YAML'
        ),
        DetectionPattern(
            name='petsc_content',
            pattern_type='content',
            pattern=r'(?i)petsc',
            confidence=0.7,
            description='PETSc keyword in content'
        )
    ]
    
    # Kokkos patterns
    kokkos_patterns = [
        DetectionPattern(
            name='kokkos_xml_parameter',
            pattern_type='xpath',
            pattern='.//Parameter[@name="framework"][@value="kokkos"]',
            confidence=0.9,
            description='Kokkos framework parameter'
        ),
        DetectionPattern(
            name='kokkos_content',
            pattern_type='content',
            pattern=r'(?i)kokkos',
            confidence=0.7,
            description='Kokkos keyword in content'
        )
    ]
    
    # Hybrid PETSc-Kokkos patterns
    hybrid_patterns = [
        DetectionPattern(
            name='hybrid_xml_parameter',
            pattern_type='xpath',
            pattern='.//Parameter[@name="framework"][@value="hybrid"]',
            confidence=0.9,
            description='Hybrid framework parameter'
        ),
        DetectionPattern(
            name='hybrid_content',
            pattern_type='content',
            pattern=r'(?i)(hybrid|petsc.*kokkos|kokkos.*petsc)',
            confidence=0.8,
            description='Hybrid framework keywords'
        )
    ]
    
    # Register all patterns
    detection_engine.register_extension_patterns('original_gcr', gcr_patterns)
    detection_engine.register_extension_patterns('petsc_only', petsc_patterns)
    detection_engine.register_extension_patterns('kokkos_only', kokkos_patterns)
    detection_engine.register_extension_patterns('hybrid_petsc_kokkos', hybrid_patterns)
    
    logger.info("Registered built-in framework detection patterns")


# Auto-register built-in patterns on module import
register_builtin_patterns()